OneShell

I fight for a brighter tomorrow

0%

[[afl-training] libxml2

[afl-training] libxml2

libxml2 是一个流行的 XML 库,这类库是非常适合用来做 fuzzing ,理由如下:

  • 经常需要解析用户提供的数据
  • 库是由不安全语言编写(例如 C、C++)
  • 无状态
  • 没有网络和文件系统交互
  • 官方提供的 API 就是很好的 fuzz 目标,无需额外去分析和识别库内部的组件关系
  • 运行速度快

这次 fuzz 挑战的目标是在库中寻找 CVE-2015-8317,需要使用 AFL 对库源代码进行编译插桩,并且加上 ASAN 选项:

1
2
3
4
git submodule init && git submodule update
cd libxml2
CC=afl-clang-fast ./autogen.sh
AFL_USE_ASAN=1 make -j 4

使用 AFL_USE_ASAN=1 是开启 ASAN 辅助,这是基于 clang 的一个内存错误检测器,可以检测到常见的内存漏洞,例如栈溢出、堆溢出、double free、uaf 等等。

编写 harness

在之前的 harness 章节就讲到,fuzz 一个库的基本流程是:

  1. 对库使用 AFL 进行编译插桩
  2. 通过相关的官方文档知道库中的 API 是如何被正常调用的
  3. 写一个类似的 harness 调用 API,使得 AFL 产生的输入可以喂给 API 执行,并编译插桩 harness
  4. 使用 afl-fuzz 对 harness 进行 fuzz

我们已经使用 afl-clang-fast 编译了 libxml2 库,那么接下来就是去官方文档中查看正常情况下正确调用库 API 的案例,libxml2 的官方文档在此处,在 fuzz 的时候可以参考此案例,对库的 xmlReadMemory 函数进行 fuzz。如下是官方提供的 API 调用案例,读取 XML 文件到树上,并释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* section: Parsing
* synopsis: Parse an XML file to a tree and free it
* purpose: Demonstrate the use of xmlReadFile() to read an XML file
* into a tree and xmlFreeDoc() to free the resulting tree
* usage: parse1 test1.xml
* test: parse1 test1.xml
* author: Daniel Veillard
* copy: see Copyright for the status of this software.
*/

#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

/**
* example1Func:
* @filename: a filename or an URL
*
* Parse the resource and free the resulting tree
*/
static void
example1Func(const char *filename) {
xmlDocPtr doc; /* the resulting document tree */

doc = xmlReadFile(filename, NULL, 0);
if (doc == NULL) {
fprintf(stderr, "Failed to parse %s\n", filename);
return;
}
xmlFreeDoc(doc);
}

int main(int argc, char **argv) {
if (argc != 2)
return(1);

/*
* this initialize the library and check potential ABI mismatches
* between the version it was compiled for and the actual shared
* library used.
*/
LIBXML_TEST_VERSION

example1Func(argv[1]);

/*
* Cleanup function for the XML library.
*/
xmlCleanupParser();
/*
* this is to debug memory for regression tests
*/
xmlMemoryDump();
return(0);
}

那么我们可以根据上面的案例写出一个 harness,这个在挑战的 ANSWERS.md 中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "libxml/parser.h"
#include "libxml/tree.h"

int main(int argc, char **argv) {
if (argc != 2){
return(1);
}

xmlInitParser();
while (__AFL_LOOP(1000)) {
xmlDocPtr doc = xmlReadFile(argv[1], NULL, 0);
if (doc != NULL) {
xmlFreeDoc(doc);
}
}
xmlCleanupParser();

return(0);
}

编写完 harness.c 后插桩编译,-I 选项是指定包含的头文件目录,然后接上 libxml2 的静态链接库,-lz 是使用 zlib 库,-lm 是使用 math 库,然后编译出来的是一个将 libxml2 静态链接的可执行文件,这样在编译插桩的时候就可以直接对 libxml2 的汇编代码进行插桩(如果之前已经对 libxml2 进行了插桩编译,应该就不需要再静态编译了)。

1
2
AFL_USE_ASAN=1 afl-clang-fast ./harness.c -I libxml2/include libxml2/.libs/libxml2.a -lz -lm -o fuzzer

undifined

编写完 harness 后,就需要使用高质量的种子来启动 afl-fuzz,afl 的源码中提供了一个不错的 XML 字典,可以就使用它来作为初始种子。

1
2
mkdir input
echo "<hi></hi>" > inout/hi.xml

然后开始 fuzz,-x 是设定 fuzzer 的字典,@@ 类似于占位符,表示输入的位置,因为 harness 使用的是 argv 作为输入。

1
afl-fuzz -i in -o out -x ~/Code/AFL/dictionaries/xml.dict ./fuzzer @@

然后让 AFL 在后台运行吧,等待结果,用虚拟机跑了一天,挖出来 16 个 crashes

undifined

处理 crashes

我们写出来的 harness 是通过命令参数读取文件,那么直接将 output/crashes 中的文件给程序,就会报错。而且因为编译的时候使用了 ASAN 标志,会有详细的报错信息提醒。跑出来的 16 个 unique creashes 都是相同的报错:

undifined

通过上图中的函数堆栈回溯,我们可以定位错误的性质是一个字节的堆溢出,而且漏洞是发生在 libxml2/parse.c 文件中。个人对堆了解得不是很深入,就在这个地方吧,埋一个坑,以后有空更新。

参考链接